﻿using System;
using System.Collections.Generic;
using Alone.GameObjects;
using Alone.GameObjects.Enemy;
using Microsoft.Xna.Framework;

namespace Alone.EnemyStates
{
    public abstract class EnemyState
    {
        private float enemySeparation = 1000f;
        private float distanceFromPlayer = 2000f;
        private float distance;

		public abstract void doAI(Enemy enemy, List<Enemy> enemies, List<BasePlayer> players);
		public abstract float evaluate(Enemy enemy, List<Enemy> enemies, BasePlayer player);

		/// <summary>
		/// Determines whether [is very near friend] [the specified enemy pos].
		/// </summary>
		/// <param name="enemyPos">The enemy pos.</param>
		/// <param name="friendPos">The friend pos.</param>
		/// <returns>
		/// 	<c>true</c> if [is very near friend] [the specified enemy pos]; otherwise, <c>false</c>.
		/// </returns>
		protected float isVeryNearFriend(Vector3 enemyPos, Vector3 friendPos)
		{
			distance = Vector3.Distance(enemyPos, friendPos);
            float fuzzy = (enemySeparation / 6 - distance) / enemySeparation / 6;

			return MathHelper.Clamp(fuzzy,0,1);
		}

		/// <summary>
		/// Determines whether [is near friend] [the specified enemy pos].
		/// </summary>
		/// <param name="enemyPos">The enemy pos.</param>
		/// <param name="friendPos">The friend pos.</param>
		/// <returns>
		/// 	<c>true</c> if [is near friend] [the specified enemy pos]; otherwise, <c>false</c>.
		/// </returns>
		protected float isNearFriend(Vector3 enemyPos, Vector3 friendPos)
		{
			distance = Vector3.Distance(enemyPos, friendPos);
			float fuzzy = (enemySeparation - distance) / enemySeparation;

            return MathHelper.Clamp(fuzzy, 0, 1);
		}

		/// <summary>
		/// Determines whether [is very near player] [the specified enemy pos].
		/// </summary>
		/// <param name="enemyPos">The enemy pos.</param>
		/// <param name="playerPos">The player pos.</param>
		/// <returns>
		/// 	<c>true</c> if [is very near player] [the specified enemy pos]; otherwise, <c>false</c>.
		/// </returns>
		protected float isVeryNearPlayer(Vector3 enemyPos, Vector3 playerPos)
		{
			distance = Vector3.Distance(enemyPos, playerPos);

			float fuzzy = (distanceFromPlayer / 8 - distance) / distanceFromPlayer / 8;

            return MathHelper.Clamp(fuzzy, 0, 1);
		}

		/// <summary>
		/// Determines whether [is near player] [the specified enemy pos].
		/// </summary>
		/// <param name="enemyPos">The enemy pos.</param>
		/// <param name="playerPos">The player pos.</param>
		/// <returns>
		/// 	<c>true</c> if [is near player] [the specified enemy pos]; otherwise, <c>false</c>.
		/// </returns>
		protected float isNearPlayer(Vector3 enemyPos, Vector3 playerPos)
		{
			distance = Vector3.Distance(enemyPos, playerPos);
			float fuzzy = (distanceFromPlayer - distance) / distanceFromPlayer;

            return MathHelper.Clamp(fuzzy, 0, 1);
		}


		/// <summary>
		/// Changes the direction.
		/// </summary>
		/// <param name="oldDir">The old dir.</param>
		/// <param name="newDir">The new dir.</param>
		/// <param name="maxTurnRadians">The max turn radians.</param>
		/// <returns></returns>
		protected float ChangeDirection(float oldDir, float newDir, float maxTurnRadians)
		{
			float angle = oldDir - newDir;
			
			if (angle < 10 && angle > -10)
				return newDir;

			if (oldDir > newDir)
				return oldDir - maxTurnRadians;

			return oldDir + maxTurnRadians;
		}

		/// <summary>
		/// Calculates the angle that an object should face, given its position, its
		/// target's position, its current angle, and its maximum turning speed.
		/// </summary>
		/// <param name="position">The position.</param>
		/// <param name="faceThis">The face this.</param>
		/// <param name="currentAngle">The current angle.</param>
		/// <param name="turnSpeed">The turn speed.</param>
		/// <returns></returns>
		protected float TurnToFace(Vector3 position, Vector3 faceThis,
			float currentAngle, float turnSpeed)
		{
			float x = faceThis.X - position.X;
			float z = faceThis.Z - position.Z;

			float desiredAngle = (float)Math.Atan2(x, z);

			float difference = WrapAngle(desiredAngle - currentAngle);

			difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);

			return WrapAngle(currentAngle + difference);
		}

		protected float TurnAwayFrom(Vector3 position, Vector3 awayFromThis,
			float currentAngle, float turnSpeed)
		{

			float x = position.X - awayFromThis.X;
			float z = position.Z - awayFromThis.Z;

			float desiredAngle = (float)Math.Atan2(x, z);
			float difference = WrapAngle(desiredAngle - currentAngle);

			difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);

			return WrapAngle(currentAngle + difference);
		}


		/// <summary>
		/// Returns the angle expressed in radians between -Pi and Pi.
		/// <param name="radians">the angle to wrap, in radians.</param>
		/// <returns>the input value expressed in radians from -Pi to Pi.</returns>
		/// </summary>
		protected float WrapAngle(float radians)
		{
			while (radians < -MathHelper.Pi)
			{
				radians += MathHelper.TwoPi;
			}
			while (radians > MathHelper.Pi)
			{
				radians -= MathHelper.TwoPi;
			}
			return radians;
		}

		public bool isOnPlane(Vector3 position)
		{
			if (position.X > 3400 || position.X < -3400 || position.Z > 3400 || position.Z < -3400)
				return false;

			return true;
		}
	}
}
